/**
 * Created by xinxinran on 2017/8/22.
 */
'use strict';
const net = require('net'),
    Protocol = require('./protocol'),
    loggerUtil = require('../utils/loggerUtil'),
    commonUtil = require('../utils/commonUtil'),
    propUtil = require('../utils/propertyUtil');

const initClientFunc = Symbol('_initClient');
class ClientManager{
    constructor(server){
        let clusterOperator, clusterProtocol, _this = this,
            clusterServer = {
                port : +propUtil.getProperty('clusterSocketPort'),
                ip : propUtil.getProperty('clusterManagerIp')
            };

        this.logger = loggerUtil.getLogger();
        this.tokenSockets = {};
        this.idSockets = {};
        this.server = server;

        // 不配置为单机运行
        if(clusterServer.port && clusterServer.ip){
            this.clusterServer = new net.Socket();

            this.clusterServer.on('data', function(data){
                clusterProtocol.receiveData(data);
            });

            this.clusterServer.on('close', function(){
                this.logger.warn('[clusterSocket]close...');
            });

            this.clusterServer.on('error', function(err){
                this.logger.warn('[clusterSocket]err...', err);
            });

            this.clusterServer.connect(clusterServer.port, clusterServer.ip, function(){
                this.logger.info('[clusterSocket]connected....');
                clusterOperator = new ClientOperator(_this, _this.clusterServer);
                clusterProtocol = new Protocol(clusterOperator, true);

            });
        }
    }

    newClient(client) {
        this.logger.info('client connected...', {ip : client.remoteAddress});
        let clientOperator = this[initClientFunc](client);
        this.idSockets[clientOperator.id] = clientOperator;
    }

    //private function
    [initClientFunc](client){
        let clientOperator = new ClientOperator(this, client), protocol = new Protocol(clientOperator), _this = this;

        client.on('data', function(data){
            protocol.receiveData(data);
            clientOperator.reCountTimer();
        });

        client.on('timeout', () => {
            this.logger.debug('[socket]timeout...');
            clientOperator.end();
        });

        client.on('error', error => {
            this.logger.debug('[socket]error...', error);
        });

        client.on('end', () => {
            this.logger.debug('[socket]end...');
        });

        client.on('close', isError => {
            clientOperator.state = 0;
            clientOperator.offline();
            delete _this.idSockets[clientOperator.id];
            delete _this.tokenSockets[clientOperator.token];
            this.logger.debug('[socket]close...', isError);
        });

        return clientOperator;
    }

    bindToken(cOperator){
        if(this.idSockets[cOperator.id]){
            delete this.idSockets[cOperator.id];
        }

        cOperator.token && (this.tokenSockets[cOperator.token] = cOperator);
    }

    unbindToken(cOperator){
        delete this.tokenSockets[cOperator.token];
        this.idSockets[cOperator.id] = cOperator;
        cOperator.token = null;
    }

    kick(tokens, time){
        tokens.forEach(token => {
            let cOperator;
            if(cOperator = this.tokenSockets[token]){
                if(cOperator.state){
                    cOperator.sendCode(2, {time : time});
                }

                this.unbindToken(cOperator);
            }
        });
    }
}

class ClientOperator{
    constructor(manager, client){
        this.router = manager.server.router;
        this.socket = client;
        this.id = commonUtil.uuid(16, 16);
        this.proxyEnd = this.end.bind(this, 'proxy timeout');
        this.timerId = -1;
        this.maxWaitTime = 40 * 1000;
        this.state = 1;
        this.manager = manager;
        this.token = null;
        this.protocol = null;

        this.logger = loggerUtil.getLogger();

        this.startTimer();
    }

    end(causeOf){
        this.logger.debug('[socket]server makes end..., cause of : ' + causeOf);
        this.socket.end();
        this.socket.destroy();
    }

    send(buffer){
        this.socket.write(buffer);
    }

    startTimer(){
        if(this.timerId < 0){
            this.timerId = setTimeout(this.proxyEnd, this.maxWaitTime);
        }
    }

    endTimer(){
        clearTimeout(this.timerId);
        this.timerId = -1;
    }

    reCountTimer(){
        this.endTimer();
        this.startTimer();
    }

    dispatchRequest (req, res , next){
        let thisRoute = this.router.getRoute(req.rurl, 'socket');
        if(!thisRoute){
            next.err && next.err('resource not found : ' + req.url);
            return;
        }

        Object.assign(req.params, thisRoute.params, {_socketId : this.id});

        res.socketOperator = this;

        thisRoute.route.handler(req, res, next);
    }

    offline(){
        let ef = function(){};
        this.dispatchRequest({
            rurl : '/offline',
            params : {},
            contentType : () => 'socket/json'
        }, {send : ef}, ef);
    }

    doCommand(cmd, cmdObj){
        switch (cmd){
            case 'delToken' :
                this.manager.kick(cmdObj.tokens, cmdObj.time);
                break;
            default : break;
        }
    }

    registerToken(token){
        this.token = token;
        this.manager.bindToken(this);
    }

    unRegisterToken(token){
        if(this.token !== token){
            next.err('token not match...');
            return;
        }

        this.manager.unbindToken(this);
    }

    sendCode(code, obj){
        obj = obj || {};
        obj.code = code;
        this.send(this.protocol.receiveBean.genPackageBuffer(4, null, obj));
    }

    noticeKick(tokens, time){
        this.manager.clusterServer.write(this.protocol.receiveBean.genPackageBuffer(4, null, {action: 'delToken',tokens : tokens, time : time}));
    }
}

module.exports = ClientManager;